// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_COMPILER_LINKAGE_H_
#define V8_COMPILER_LINKAGE_H_

#include "src/base/compiler-specific.h"
#include "src/base/flags.h"
#include "src/compiler/frame.h"
#include "src/compiler/operator.h"
#include "src/globals.h"
#include "src/interface-descriptors.h"
#include "src/machine-type.h"
#include "src/register-arch.h"
#include "src/reglist.h"
#include "src/runtime/runtime.h"
#include "src/signature.h"
#include "src/zone/zone.h"

namespace v8 {
namespace internal {

    class CallInterfaceDescriptor;
    class OptimizedCompilationInfo;

    namespace compiler {

        const RegList kNoCalleeSaved = 0;

        class Node;
        class OsrHelper;

        // Describes the location for a parameter or a return value to a call.
        class LinkageLocation {
        public:
            bool operator==(const LinkageLocation& other) const
            {
                return bit_field_ == other.bit_field_;
            }

            bool operator!=(const LinkageLocation& other) const
            {
                return !(*this == other);
            }

            static LinkageLocation ForAnyRegister(
                MachineType type = MachineType::None())
            {
                return LinkageLocation(REGISTER, ANY_REGISTER, type);
            }

            static LinkageLocation ForRegister(int32_t reg,
                MachineType type = MachineType::None())
            {
                DCHECK_LE(0, reg);
                return LinkageLocation(REGISTER, reg, type);
            }

            static LinkageLocation ForCallerFrameSlot(int32_t slot, MachineType type)
            {
                DCHECK_GT(0, slot);
                return LinkageLocation(STACK_SLOT, slot, type);
            }

            static LinkageLocation ForCalleeFrameSlot(int32_t slot, MachineType type)
            {
                // TODO(titzer): bailout instead of crashing here.
                DCHECK(slot >= 0 && slot < LinkageLocation::MAX_STACK_SLOT);
                return LinkageLocation(STACK_SLOT, slot, type);
            }

            static LinkageLocation ForSavedCallerReturnAddress()
            {
                return ForCalleeFrameSlot((StandardFrameConstants::kCallerPCOffset - StandardFrameConstants::kCallerPCOffset) / kSystemPointerSize,
                    MachineType::Pointer());
            }

            static LinkageLocation ForSavedCallerFramePtr()
            {
                return ForCalleeFrameSlot((StandardFrameConstants::kCallerPCOffset - StandardFrameConstants::kCallerFPOffset) / kSystemPointerSize,
                    MachineType::Pointer());
            }

            static LinkageLocation ForSavedCallerConstantPool()
            {
                DCHECK(V8_EMBEDDED_CONSTANT_POOL);
                return ForCalleeFrameSlot((StandardFrameConstants::kCallerPCOffset - StandardFrameConstants::kConstantPoolOffset) / kSystemPointerSize,
                    MachineType::AnyTagged());
            }

            static LinkageLocation ForSavedCallerFunction()
            {
                return ForCalleeFrameSlot((StandardFrameConstants::kCallerPCOffset - StandardFrameConstants::kFunctionOffset) / kSystemPointerSize,
                    MachineType::AnyTagged());
            }

            static LinkageLocation ConvertToTailCallerLocation(
                LinkageLocation caller_location, int stack_param_delta)
            {
                if (!caller_location.IsRegister()) {
                    return LinkageLocation(STACK_SLOT,
                        caller_location.GetLocation() + stack_param_delta,
                        caller_location.GetType());
                }
                return caller_location;
            }

            MachineType GetType() const { return machine_type_; }

            int GetSize() const
            {
                return 1 << ElementSizeLog2Of(GetType().representation());
            }

            int GetSizeInPointers() const
            {
                // Round up
                return (GetSize() + kSystemPointerSize - 1) / kSystemPointerSize;
            }

            int32_t GetLocation() const
            {
                // We can't use LocationField::decode here because it doesn't work for
                // negative values!
                return static_cast<int32_t>(bit_field_ & LocationField::kMask) >> LocationField::kShift;
            }

            bool IsRegister() const { return TypeField::decode(bit_field_) == REGISTER; }
            bool IsAnyRegister() const
            {
                return IsRegister() && GetLocation() == ANY_REGISTER;
            }
            bool IsCallerFrameSlot() const { return !IsRegister() && GetLocation() < 0; }
            bool IsCalleeFrameSlot() const { return !IsRegister() && GetLocation() >= 0; }

            int32_t AsRegister() const
            {
                DCHECK(IsRegister());
                return GetLocation();
            }
            int32_t AsCallerFrameSlot() const
            {
                DCHECK(IsCallerFrameSlot());
                return GetLocation();
            }
            int32_t AsCalleeFrameSlot() const
            {
                DCHECK(IsCalleeFrameSlot());
                return GetLocation();
            }

        private:
            enum LocationType { REGISTER,
                STACK_SLOT };

            class TypeField : public BitField<LocationType, 0, 1> {
            };
            class LocationField : public BitField<int32_t, TypeField::kNext, 31> {
            };

            static constexpr int32_t ANY_REGISTER = -1;
            static constexpr int32_t MAX_STACK_SLOT = 32767;

            LinkageLocation(LocationType type, int32_t location,
                MachineType machine_type)
            {
                bit_field_ = TypeField::encode(type) |
                    // {location} can be -1 (ANY_REGISTER).
                    ((static_cast<uint32_t>(location) << LocationField::kShift) & LocationField::kMask);
                machine_type_ = machine_type;
            }

            int32_t bit_field_;
            MachineType machine_type_;
        };

        using LocationSignature = Signature<LinkageLocation>;

        // Describes a call to various parts of the compiler. Every call has the notion
        // of a "target", which is the first input to the call.
        class V8_EXPORT_PRIVATE CallDescriptor final
            : public NON_EXPORTED_BASE(ZoneObject) {
        public:
            // Describes the kind of this call, which determines the target.
            enum Kind {
                kCallCodeObject, // target is a Code object
                kCallJSFunction, // target is a JSFunction object
                kCallAddress, // target is a machine pointer
                kCallWasmFunction, // target is a wasm function
                kCallWasmImportWrapper, // target is a wasm import wrapper
                kCallBuiltinPointer, // target is a builtin pointer
            };

            enum Flag {
                kNoFlags = 0u,
                kNeedsFrameState = 1u << 0,
                kHasExceptionHandler = 1u << 1,
                kCanUseRoots = 1u << 2,
                // Causes the code generator to initialize the root register.
                kInitializeRootRegister = 1u << 3,
                // Does not ever try to allocate space on our heap.
                kNoAllocate = 1u << 4,
                // Push argument count as part of function prologue.
                kPushArgumentCount = 1u << 5,
                // Use retpoline for this call if indirect.
                kRetpoline = 1u << 6,
                // Use the kJavaScriptCallCodeStartRegister (fixed) register for the
                // indirect target address when calling.
                kFixedTargetRegister = 1u << 7,
                kAllowCallThroughSlot = 1u << 8
            };
            using Flags = base::Flags<Flag>;

            CallDescriptor(Kind kind, MachineType target_type, LinkageLocation target_loc,
                LocationSignature* location_sig, size_t stack_param_count,
                Operator::Properties properties,
                RegList callee_saved_registers,
                RegList callee_saved_fp_registers, Flags flags,
                const char* debug_name = "",
                const RegList allocatable_registers = 0,
                size_t stack_return_count = 0)
                : kind_(kind)
                , target_type_(target_type)
                , target_loc_(target_loc)
                , location_sig_(location_sig)
                , stack_param_count_(stack_param_count)
                , stack_return_count_(stack_return_count)
                , properties_(properties)
                , callee_saved_registers_(callee_saved_registers)
                , callee_saved_fp_registers_(callee_saved_fp_registers)
                , allocatable_registers_(allocatable_registers)
                , flags_(flags)
                , debug_name_(debug_name)
            {
            }

            // Returns the kind of this call.
            Kind kind() const { return kind_; }

            // Returns {true} if this descriptor is a call to a C function.
            bool IsCFunctionCall() const { return kind_ == kCallAddress; }

            // Returns {true} if this descriptor is a call to a JSFunction.
            bool IsJSFunctionCall() const { return kind_ == kCallJSFunction; }

            // Returns {true} if this descriptor is a call to a WebAssembly function.
            bool IsWasmFunctionCall() const { return kind_ == kCallWasmFunction; }

            // Returns {true} if this descriptor is a call to a WebAssembly function.
            bool IsWasmImportWrapper() const { return kind_ == kCallWasmImportWrapper; }

            bool RequiresFrameAsIncoming() const
            {
                return IsCFunctionCall() || IsJSFunctionCall() || IsWasmFunctionCall();
            }

            // The number of return values from this call.
            size_t ReturnCount() const { return location_sig_->return_count(); }

            // The number of C parameters to this call.
            size_t ParameterCount() const { return location_sig_->parameter_count(); }

            // The number of stack parameters to the call.
            size_t StackParameterCount() const { return stack_param_count_; }

            // The number of stack return values from the call.
            size_t StackReturnCount() const { return stack_return_count_; }

            // The number of parameters to the JS function call.
            size_t JSParameterCount() const
            {
                DCHECK(IsJSFunctionCall());
                return stack_param_count_;
            }

            // The total number of inputs to this call, which includes the target,
            // receiver, context, etc.
            // TODO(titzer): this should input the framestate input too.
            size_t InputCount() const { return 1 + location_sig_->parameter_count(); }

            size_t FrameStateCount() const { return NeedsFrameState() ? 1 : 0; }

            Flags flags() const { return flags_; }

            bool NeedsFrameState() const { return flags() & kNeedsFrameState; }
            bool PushArgumentCount() const { return flags() & kPushArgumentCount; }
            bool InitializeRootRegister() const
            {
                return flags() & kInitializeRootRegister;
            }

            LinkageLocation GetReturnLocation(size_t index) const
            {
                return location_sig_->GetReturn(index);
            }

            LinkageLocation GetInputLocation(size_t index) const
            {
                if (index == 0)
                    return target_loc_;
                return location_sig_->GetParam(index - 1);
            }

            MachineSignature* GetMachineSignature(Zone* zone) const;

            MachineType GetReturnType(size_t index) const
            {
                return location_sig_->GetReturn(index).GetType();
            }

            MachineType GetInputType(size_t index) const
            {
                if (index == 0)
                    return target_type_;
                return location_sig_->GetParam(index - 1).GetType();
            }

            MachineType GetParameterType(size_t index) const
            {
                return location_sig_->GetParam(index).GetType();
            }

            // Operator properties describe how this call can be optimized, if at all.
            Operator::Properties properties() const { return properties_; }

            // Get the callee-saved registers, if any, across this call.
            RegList CalleeSavedRegisters() const { return callee_saved_registers_; }

            // Get the callee-saved FP registers, if any, across this call.
            RegList CalleeSavedFPRegisters() const { return callee_saved_fp_registers_; }

            const char* debug_name() const { return debug_name_; }

            bool UsesOnlyRegisters() const;

            bool HasSameReturnLocationsAs(const CallDescriptor* other) const;

            // Returns the first stack slot that is not used by the stack parameters.
            int GetFirstUnusedStackSlot() const;

            int GetStackParameterDelta(const CallDescriptor* tail_caller) const;

            int GetTaggedParameterSlots() const;

            bool CanTailCall(const Node* call) const;

            int CalculateFixedFrameSize() const;

            RegList AllocatableRegisters() const { return allocatable_registers_; }

            bool HasRestrictedAllocatableRegisters() const
            {
                return allocatable_registers_ != 0;
            }

            void set_save_fp_mode(SaveFPRegsMode mode) { save_fp_mode_ = mode; }

            SaveFPRegsMode get_save_fp_mode() const { return save_fp_mode_; }

        private:
            friend class Linkage;
            SaveFPRegsMode save_fp_mode_ = kSaveFPRegs;

            const Kind kind_;
            const MachineType target_type_;
            const LinkageLocation target_loc_;
            const LocationSignature* const location_sig_;
            const size_t stack_param_count_;
            const size_t stack_return_count_;
            const Operator::Properties properties_;
            const RegList callee_saved_registers_;
            const RegList callee_saved_fp_registers_;
            // Non-zero value means restricting the set of allocatable registers for
            // register allocator to use.
            const RegList allocatable_registers_;
            const Flags flags_;
            const char* const debug_name_;

            DISALLOW_COPY_AND_ASSIGN(CallDescriptor);
        };

        DEFINE_OPERATORS_FOR_FLAGS(CallDescriptor::Flags)

        std::ostream& operator<<(std::ostream& os, const CallDescriptor& d);
        V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
            const CallDescriptor::Kind& k);

        // Defines the linkage for a compilation, including the calling conventions
        // for incoming parameters and return value(s) as well as the outgoing calling
        // convention for any kind of call. Linkage is generally architecture-specific.
        //
        // Can be used to translate {arg_index} (i.e. index of the call node input) as
        // well as {param_index} (i.e. as stored in parameter nodes) into an operator
        // representing the architecture-specific location. The following call node
        // layouts are supported (where {n} is the number of value inputs):
        //
        //                        #0          #1     #2     [...]             #n
        // Call[CodeStub]         code,       arg 1, arg 2, [...],            context
        // Call[JSFunction]       function,   rcvr,  arg 1, [...], new, #arg, context
        // Call[Runtime]          CEntry,     arg 1, arg 2, [...], fun, #arg, context
        // Call[BytecodeDispatch] address,    arg 1, arg 2, [...]
        class V8_EXPORT_PRIVATE Linkage : public NON_EXPORTED_BASE(ZoneObject) {
        public:
            explicit Linkage(CallDescriptor* incoming)
                : incoming_(incoming)
            {
            }

            static CallDescriptor* ComputeIncoming(Zone* zone,
                OptimizedCompilationInfo* info);

            // The call descriptor for this compilation unit describes the locations
            // of incoming parameters and the outgoing return value(s).
            CallDescriptor* GetIncomingDescriptor() const { return incoming_; }
            static CallDescriptor* GetJSCallDescriptor(Zone* zone, bool is_osr,
                int parameter_count,
                CallDescriptor::Flags flags);

            static CallDescriptor* GetRuntimeCallDescriptor(
                Zone* zone, Runtime::FunctionId function, int js_parameter_count,
                Operator::Properties properties, CallDescriptor::Flags flags);

            static CallDescriptor* GetCEntryStubCallDescriptor(
                Zone* zone, int return_count, int js_parameter_count,
                const char* debug_name, Operator::Properties properties,
                CallDescriptor::Flags flags);

            static CallDescriptor* GetStubCallDescriptor(
                Zone* zone, const CallInterfaceDescriptor& descriptor,
                int stack_parameter_count, CallDescriptor::Flags flags,
                Operator::Properties properties = Operator::kNoProperties,
                StubCallMode stub_mode = StubCallMode::kCallCodeObject);

            static CallDescriptor* GetBytecodeDispatchCallDescriptor(
                Zone* zone, const CallInterfaceDescriptor& descriptor,
                int stack_parameter_count);

            // Creates a call descriptor for simplified C calls that is appropriate
            // for the host platform. This simplified calling convention only supports
            // integers and pointers of one word size each, i.e. no floating point,
            // structs, pointers to members, etc.
            static CallDescriptor* GetSimplifiedCDescriptor(
                Zone* zone, const MachineSignature* sig,
                bool set_initialize_root_flag = false);

            // Get the location of an (incoming) parameter to this function.
            LinkageLocation GetParameterLocation(int index) const
            {
                return incoming_->GetInputLocation(index + 1); // + 1 to skip target.
            }

            // Get the machine type of an (incoming) parameter to this function.
            MachineType GetParameterType(int index) const
            {
                return incoming_->GetInputType(index + 1); // + 1 to skip target.
            }

            // Get the location where this function should place its return value.
            LinkageLocation GetReturnLocation(size_t index = 0) const
            {
                return incoming_->GetReturnLocation(index);
            }

            // Get the machine type of this function's return value.
            MachineType GetReturnType(size_t index = 0) const
            {
                return incoming_->GetReturnType(index);
            }

            bool ParameterHasSecondaryLocation(int index) const;
            LinkageLocation GetParameterSecondaryLocation(int index) const;

            static bool NeedsFrameStateInput(Runtime::FunctionId function);

            // Get the location where an incoming OSR value is stored.
            LinkageLocation GetOsrValueLocation(int index) const;

            // A special {Parameter} index for Stub Calls that represents context.
            static int GetStubCallContextParamIndex(int parameter_count)
            {
                return parameter_count + 0; // Parameter (arity + 0) is special.
            }

            // A special {Parameter} index for JSCalls that represents the new target.
            static int GetJSCallNewTargetParamIndex(int parameter_count)
            {
                return parameter_count + 0; // Parameter (arity + 0) is special.
            }

            // A special {Parameter} index for JSCalls that represents the argument count.
            static int GetJSCallArgCountParamIndex(int parameter_count)
            {
                return parameter_count + 1; // Parameter (arity + 1) is special.
            }

            // A special {Parameter} index for JSCalls that represents the context.
            static int GetJSCallContextParamIndex(int parameter_count)
            {
                return parameter_count + 2; // Parameter (arity + 2) is special.
            }

            // A special {Parameter} index for JSCalls that represents the closure.
            static const int kJSCallClosureParamIndex = -1;

            // A special {OsrValue} index to indicate the context spill slot.
            static const int kOsrContextSpillSlotIndex = -1;

            // A special {OsrValue} index to indicate the accumulator register.
            static const int kOsrAccumulatorRegisterIndex = -1;

        private:
            CallDescriptor* const incoming_;

            DISALLOW_COPY_AND_ASSIGN(Linkage);
        };

    } // namespace compiler
} // namespace internal
} // namespace v8

#endif // V8_COMPILER_LINKAGE_H_
